package com.mybatis.boost.extend.session;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.dao.support.PersistenceExceptionTranslator;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

import static com.google.common.collect.Maps.newConcurrentMap;
import static com.mybatis.boost.mapper.provider.SingleMapperTemplate.getMapperName;
import static com.mybatis.boost.tool.ReflectUtil.findMethods;
import static tk.mybatis.mapper.mapperhelper.MapperTemplate.getMethodName;

/**
 * @author jearton
 * @since 2017/2/13
 */
@Slf4j
public class SqlSessionTemplate extends org.mybatis.spring.SqlSessionTemplate implements SqlSession {

    private Map<String, Boolean> optionalCache = newConcurrentMap();

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, PersistenceExceptionTranslator exceptionTranslator) {
        super(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType(), exceptionTranslator);
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        super(sqlSessionFactory, executorType, exceptionTranslator);
    }

    @Override
    public <T> T selectOne(String statement) {
        return _selectOneOrSelectOptional(statement, () -> super.selectOne(statement));
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return _selectOneOrSelectOptional(statement, () -> super.selectOne(statement, parameter));
    }

    @SuppressWarnings("unchecked")
    private <T> T _selectOneOrSelectOptional(String statement, Supplier<T> selectOne) {
        T t = selectOne.get();
        // 判断方法的返回值是否为Optional
        if (returnOptional(statement)) {
            Optional<?> optional = Optional.ofNullable(t);
            return (T) optional;
        } else {
            return t;
        }
    }

    private boolean returnOptional(String statement) {
        if (optionalCache.containsKey(statement)) {
            return optionalCache.get(statement);
        }
        return getConfiguration().getMapperRegistry().getMappers()
                .stream()
                .filter(mapper -> Objects.equals(mapper.getCanonicalName(), getMapperName(statement)))
                .findFirst()
                .map(mapperInterface -> {
                    String methodName = getMethodName(statement);
                    List<Method> methods = findMethods(mapperInterface, methodName, false);
                    if (methods.size() > 0) {
                        long count = methods.stream().filter(method -> method.getReturnType() == Optional.class).count();
                        if (count == 0) {
                            optionalCache.put(statement, false);
                            return false;
                        }
                        if (count == methods.size()) {
                            optionalCache.put(statement, true);
                            return true;
                        } else {
                            // 如果mapper接口有重载方法并且它们的返回值除了Optional还有其它类型，则不允许
                            String msg = "method should not overload except default method in " + mapperInterface + "." + methodName;
                            log.error(msg);
                            throw new BindingException(msg);
                        }
                    }
                    optionalCache.put(statement, false);
                    return false;
                }).orElseGet(() -> {
                    optionalCache.put(statement, false);
                    return false;
                });
    }
}
